package com.example.demo.service.exam.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.example.demo.base.config.auth.UserInfoShareHolder;
import com.example.demo.base.login.dto.UserCache;
import com.example.demo.domain.CombineClause;
import com.example.demo.domain.PageResult;
import com.example.demo.domain.SingleClause;
import com.example.demo.domain.entity.User;
import com.example.demo.domain.entity.VojObjectiveQuestion;
import com.example.demo.domain.entity.VojObjectiveQuestionAnswer;
import com.example.demo.domain.entity.VojSubmissions;
import com.example.demo.domain.exam.Exam;
import com.example.demo.domain.exam.ExamSubmitLog;
import com.example.demo.domain.exam.ExamUserAnswerOption;
import com.example.demo.domain.exam.ExamUserSingleQuesScore;
import com.example.demo.domain.exam.ExamClassRelation;
import com.example.demo.domain.exam.ExamPaper;
import com.example.demo.domain.exam.ExamUserScore;
import com.example.demo.domain.exam.Paging;
import com.example.demo.domain.exam.QueryModel;
import com.example.demo.domain.exam.Sorting;
import com.example.demo.domain.exam.UserExamAns;
import com.example.demo.domain.exam.condition.ConditionParser;
import com.example.demo.domain.exam.condition.ExamCondition;
import com.example.demo.dto.ExamUserSingleQuesScoreDTO;
import com.example.demo.dto.ExamClassStaffDTO;
import com.example.demo.dto.ObjectiveQuestionDTO;
import com.example.demo.dto.OneClickReviewDTO;
import com.example.demo.dto.PortraitDto.AbnormalRiskWarningDto;
import com.example.demo.dto.PortraitDto.CodeCapacity;
import com.example.demo.dto.PortraitDto.CodeSummarizeDto;
import com.example.demo.dto.PortraitDto.ComprehensiveEvaluationDto;
import com.example.demo.dto.PortraitDto.ExamPortraitDto;
import com.example.demo.dto.PortraitDto.QuesTionIntervalDto;
import com.example.demo.dto.PortraitDto.RepetitionRateDto;
import com.example.demo.dto.PortraitDto.SkillMapDto;
import com.example.demo.dto.ProgrammingQuestionDTO;
import com.example.demo.dto.RepeatDTO;
import com.example.demo.dto.ResetIpDTO;
import com.example.demo.enums.*;
import com.example.demo.exception.ExceptionUtils;
import com.example.demo.mapper.ExamMapper;
import com.example.demo.mapper.UserMapper;
import com.example.demo.mapper.VojSubmissionsMapper;
import com.example.demo.service.Clause;
import com.example.demo.service.CompanyService;
import com.example.demo.service.Portrait.PortraitService;
import com.example.demo.service.Portrait.impl.PortraitServiceImpl;
import com.example.demo.service.exam.ExamSubmitLogService;
import com.example.demo.service.exam.ExamUserSingleQuesScoreService;
import com.example.demo.service.exam.ExamPaperService;
import com.example.demo.service.exam.ExamUserScoreService;
import com.example.demo.service.exam.UserExamAnswerService;
import com.example.demo.service.impl.BaseServiceImpl;
import com.example.demo.service.exam.ExamClassRelationService;
import com.example.demo.service.exam.ExamClassService;
import com.example.demo.service.exam.ExamService;
import com.example.demo.tool.CodeRegularityUtil;
import com.example.demo.tool.RepetitionRate;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.io.Console;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.example.demo.enums.QuestionType.PROGRAMMING_QUESTION;


@Service
@RequiredArgsConstructor
public class ExamServiceImpl extends BaseServiceImpl<ExamMapper, Exam> implements ExamService {

    // 打印
    private static final Logger LOGGER = LoggerFactory.getLogger(ExamServiceImpl.class);

    private final ExamMapper mapper;

    private final ExamClassService examClassService;

    private final ExamPaperService examPaperService;

    private final ExamClassRelationService examClassRelationService;

    private final CompanyService companyService;

    private final ExamUserSingleQuesScoreService examUserSingleQuesScoreService;

    private final UserExamAnswerService userExamAnswerService;

    private final ExamUserScoreService examUserScoreService;

    private final PortraitService portraitService;

    private final ExamSubmitLogService examSubmitLogService;

    private final RedisTemplate<Object, Object> redisTemplate;

    private final UserMapper userMapper;

    private final VojSubmissionsMapper vojSubmissionsMapper;

    private final static Integer ONE_MINUTES = 60 * 1000;

    private final static String USER_ANSWER_STAGING_HASH = "__answer";

    private final static String BATCH_ONE_CLICK = "__batch";

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createExam(Exam exam) {
        checkEntity(exam);
        Timestamp startTime = exam.getStartTime();
        Timestamp endTime = exam.getEndTime();
        Timestamp currentTime = new Timestamp(System.currentTimeMillis());
//        if (startTime == null || startTime.before(currentTime)) {
//            throw ExceptionUtils.exception("考试开考时间不正确");
//        }
//        if (endTime == null || endTime.before(startTime)) {
//            throw ExceptionUtils.exception("考试结束时间不正确");
//        }
        User user = UserInfoShareHolder.getUserInfo().getUser();
        exam.setCreatorId(user.getId());
        exam.setCompanyId(UserInfoShareHolder.getUserInfo().getCompanyId());
        //Long durationTime = (exam.getEndTime().getTime() - exam.getStartTime().getTime()) / ONE_MINUTES;
        //exam.setDurationTime(durationTime);
        exam.setCreateTime(new Timestamp(System.currentTimeMillis()));
        exam.setExamStatus(ExamStatus.PRE_EXAM);
        exam.setAttendNum(0);
        List<ExamClassRelation> relations = examClassRelationService.getExamClassRelationsByClassId(exam.getClassId());
        exam.setApplicantNum(relations.size());
        Map<Long, String> map = examClassService.getUserByUserIds(relations.stream().map(ExamClassRelation::getUserId).collect(Collectors.toList())).stream().collect(Collectors.toMap(User::getId, User::getUsername));
        mapper.insert(exam);
        List<ExamUserScore> list = relations.stream().map(e -> {
            ExamUserScore examUserScore = new ExamUserScore();
            examUserScore.setExamId(exam.getId());
            examUserScore.setExamName(exam.getExamName());
            examUserScore.setUserId(e.getUserId());
            examUserScore.setClassId(e.getClassId());
            examUserScore.setStatus(ExamScoreStatus.NOT_STARTED);
            examUserScore.setUsername(map.get(e.getUserId()));
            examUserScore.setType(SubmitType.NOT_SUBMIT);
            return examUserScore;
        }).collect(Collectors.toList());
        examUserScoreService.insertList(list);
    }

    private void checkEntity(Exam entity) {
        String examName = entity.getExamName();
        Long classId = entity.getClassId();
        Long paperId = entity.getPaperId();
        ExamType examType = entity.getExamType();
        LanguageType languageType = entity.getLanguageType();
        if (StringUtils.isBlank(examName)) {
            throw ExceptionUtils.exception("考试标题不能为空");
        }
        CombineClause clauses = CombineClause.and(
                SingleClause.equal("exam_name", examName),
                SingleClause.equal("company_id", UserInfoShareHolder.getUserInfo().getCompanyId()));
        if (entity.getId() != null) {
            clauses.add(SingleClause.notEqual("id", entity.getId()));
        }
        boolean exists = super.exists(clauses);
        if (exists) {
            throw ExceptionUtils.exception("存在同名考试：%s", examName);
        }
        if (classId == null) {
            throw ExceptionUtils.exception("考试班级不能为空");
        }
        if (paperId == null) {
            throw ExceptionUtils.exception("考试试卷不能为空");
        }
        if (examType == null) {
            throw ExceptionUtils.exception("考试类型不正确");
        }
        if (languageType == null) {
            throw ExceptionUtils.exception("考试采用语言不正确");
        }
    }

    @Override
    public List<ExamClassStaffDTO> getExamStaff(Long id) {
        if (id == null) {
            throw ExceptionUtils.exception("所查询考试不能为空");
        }
        Exam exam = Optional.ofNullable(mapper.selectById(id)).orElseThrow(() -> ExceptionUtils.exception("考试已不存在"));
        Long classId = exam.getClassId();
        return examClassService.getStaff(classId);
    }

    public void updateExam(Exam exam) {
        Long id = exam.getId();
        if (id == null) {
            throw ExceptionUtils.exception("所更新考试不能为空");
        }
        this.checkEntity(exam);
        mapper.updateById(exam);
    }

    @Override
    public void updateExams(List<Exam> exams) {
        if (CollectionUtils.isEmpty(exams)) {
            return;
        }
        super.saveOrUpdateBatch(exams);
    }

    @Override
    public Exam getExamById(Long examId) {
        if (examId == null) {
            throw ExceptionUtils.exception("考试标识不能为空");
        }
        return mapper.selectById(examId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reviewExamPaper(ExamUserSingleQuesScoreDTO dto) {
        Long examId = Optional.ofNullable(dto.getExamId()).orElseThrow(() -> ExceptionUtils.exception("考试标识不能为空"));
        Long userId = Optional.ofNullable(dto.getUserId()).orElseThrow(() -> ExceptionUtils.exception("用户标识不能为空"));
        Long classId = Optional.ofNullable(dto.getClassId()).orElseThrow(() -> ExceptionUtils.exception("用户标识不能为空"));
        List<ExamUserSingleQuesScore> situations = dto.getSituations();
        Exam exam = Optional.ofNullable(mapper.selectById(examId)).orElseThrow(() -> ExceptionUtils.exception("考试不存在"));
        ExamUserScore examUserScore = Optional.ofNullable(examUserScoreService.getByExamUserId(examId, userId)).orElseThrow(() -> ExceptionUtils.exception("考试成绩不存在"));
        Long paperId = exam.getPaperId();
        ExamPaper examPaper = examPaperService.getById(paperId);
        if (CollectionUtils.isEmpty(situations)) {
            return;
        }
        situations.forEach(e -> {
            e.setExamId(examId);
            e.setUserId(userId);
            e.setClassId(classId);
        });
        examUserSingleQuesScoreService.insertList(situations, examPaper.getTotalScore());
        examUserScore.setScore(situations.stream().map(ExamUserSingleQuesScore::getScore).reduce(0d, Double::sum));
        examUserScore.setStatus(ExamScoreStatus.RATED);
        examUserScoreService.insertOrUpdate(examUserScore);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void oneClickReview(OneClickReviewDTO dto) {
        Long examId = Optional.ofNullable(dto.getExamId()).orElseThrow(() -> ExceptionUtils.exception("考试标识不能为空"));
        Long userId = Optional.ofNullable(dto.getUserId()).orElseThrow(() -> ExceptionUtils.exception("用户标识不能为空"));
        Exam exam = mapper.selectById(examId);
        ExamPaper examPaper = examPaperService.getExamPaperDetail(exam.getPaperId(), PaperType.ALL_QUESTIONS);
        Integer totalScore = examPaper.getTotalScore();
        List<ObjectiveQuestionDTO> questions = examPaper.getObjectiveQuestions();

        // percentageMap 客观题得分占比
        Map<Long, Double> percentageMap = questions.stream().collect(Collectors.toMap(ObjectiveQuestionDTO::getId, ObjectiveQuestionDTO::getPercentage));
        Map<Long, List<VojObjectiveQuestionAnswer>> map = questions.stream().collect(Collectors.toMap(ObjectiveQuestionDTO::getId, VojObjectiveQuestion::getAnswers));
        List<UserExamAns> examAns = userExamAnswerService.getExamAns(examId, userId).stream().filter(e -> e.getQuestionType() != QuestionType.PROGRAMMING_QUESTION).collect(Collectors.toList());
        List<ExamUserSingleQuesScore> scores = new ArrayList<>();
        for (UserExamAns e : examAns) {
            Long questionId = e.getQuestionId();
            Double percentage = percentageMap.get(questionId);
            // 用户答案
            List<ExamUserAnswerOption> options = e.getOptions();
            // 选择题正确答案
            Map<Long, Boolean> answerMap = map.get(questionId).stream().collect(Collectors.toMap(VojObjectiveQuestionAnswer::getOptionId, v -> true));
            ExamUserSingleQuesScore singleQuesScore = e.getSingleQuesScore();
            int correctNum = 0;
            for (ExamUserAnswerOption option : options) {  // 选择题打分
                if (!answerMap.containsKey(option.getOptionId())) {
                    correctNum = 0;
                    break;
                }
                correctNum = correctNum + 1;
            }
            singleQuesScore.setScore(correctNum * 1.0 / answerMap.size() * percentage * totalScore);
            scores.add(singleQuesScore);
        }
        examUserSingleQuesScoreService.insertOrUpdateList(scores);
        ExamUserScore examUserScore = examUserScoreService.getByExamUserId(examId, userId);
        // 添加编程题分数
        scores.addAll(examUserSingleQuesScoreService.getByUserExamId(userId, examId).stream().filter(e -> e.getQuestionType() == QuestionType.PROGRAMMING_QUESTION).collect(Collectors.toList()));
        // 求分数和
        Double sum = scores.stream().map(ExamUserSingleQuesScore::getScore).reduce(0d, Double::sum);
        examUserScore.setScore(sum);
        examUserScore.setStatus(ExamScoreStatus.ONE_CLICK_RATED);
        examUserScoreService.insertOrUpdate(examUserScore);
    }

    @Override
    public List<Exam> getExamByClassId(Long classId) {
        Optional.ofNullable(classId).orElseThrow(() -> ExceptionUtils.exception("考试标识不能为空"));
        return super.selectList(SingleClause.equal("class_id", classId));
    }

    @Override
    public void resetIp(ResetIpDTO dto) {
        Long examId = Optional.ofNullable(dto.getExamId()).orElseThrow(() -> ExceptionUtils.exception("考试标识不能为空"));
        Long userId = Optional.ofNullable(dto.getUserId()).orElseThrow(() -> ExceptionUtils.exception("用户标识不能为空"));
        ExamUserScore examUserScore = examUserScoreService.getByExamUserId(examId, userId);
        examUserScore.setIp("");
        examUserScoreService.insertOrUpdate(examUserScore);
    }

    @Override
    public PageResult<Exam> page(QueryModel<ExamCondition> query) {
        UserCache userInfo = UserInfoShareHolder.getUserInfo();
        Long companyId = userInfo.getCompanyId();
        ExamCondition examCondition = Optional.ofNullable(query.getCondition()).orElse(new ExamCondition());
        query.setCondition(examCondition);
        examCondition.setCompanyId(companyId);
        examCondition.setCreatorId(userInfo.getUser().getId());
        return page(query.getCondition(), query.getPaging(), query.getSorts());
    }

    @Override
    public PageResult<Exam> pageForUser(QueryModel<ExamCondition> query) {
        return page(query.getCondition(), query.getPaging(), query.getSorts());
    }

    private PageResult<Exam> page(ExamCondition condition, Paging paging, Sorting[] sorts) {
        // 添加创建时间倒序排序
        Sorting sortByCreateTimeDesc = Sorting.descending("create_time");
        Sorting[] combinedSorts = Stream.concat(
                        Stream.of(sortByCreateTimeDesc),
                        Optional.ofNullable(sorts)  // 如果 sorts 为 null，就使用空数组
                                .map(Arrays::stream)
                                .orElse(Stream.empty())  // 如果 sorts 为 null，返回一个空流
                )
                .toArray(Sorting[]::new);
        Clause clause = CombineClause.and(
                ConditionParser.parser(condition),
                SingleClause.equal("disabled", false)
        );
        PageResult<Exam> page = super.selectPage(clause, paging, combinedSorts);
        List<Exam> exams = page.getData();
        List<Long> classIds = exams.stream().map(Exam::getClassId).collect(Collectors.toList());
        List<Long> paperIds = exams.stream().map(Exam::getPaperId).collect(Collectors.toList());
        List<Long> companyIds = exams.stream().map(Exam::getCompanyId).collect(Collectors.toList());
        Map<Long, String> classNameMap = examClassService.getNameMapByIds(classIds);
        Map<Long, String> paperNameMap = examPaperService.getNameMapByIds(paperIds);
        Map<Long, String> companyNameMap = companyService.getNameMapByIds(companyIds);
        exams.forEach(e -> {
            e.setClassName(classNameMap.get(e.getClassId()));
            e.setPaperName(paperNameMap.get(e.getPaperId()));
            e.setCompanyName(companyNameMap.get(e.getCompanyId()));
        });
        return page;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resetExam(Long examId, Long userId) {
        // 删除用户答案
        userExamAnswerService.deleteByExamUserId(examId, userId);
        // 最后删除缓存中的数据
        redisTemplate.opsForHash().delete(USER_ANSWER_STAGING_HASH, examId + "" + userId);
        // 删除用户提交记录
        examSubmitLogService.deleteByExamUserId(examId, userId);
    }

    // 批量阅卷，阅卷完后把数据传给用户画像接口
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchOneClickReview(Long examId) {
        // 限定评分间隔为10分钟
        limit(examId);
        Optional.ofNullable(examId).orElseThrow(() -> ExceptionUtils.exception("考试标识不能为空"));
        // 获取考试信息，分数答案....
        Exam exam = mapper.selectById(examId);
        ExamPaper examPaper = examPaperService.getExamPaperDetail(exam.getPaperId(), PaperType.ALL_QUESTIONS);
        // 获取试卷总分
        Integer totalScore = examPaper.getTotalScore();
        List<ObjectiveQuestionDTO> questions = examPaper.getObjectiveQuestions();
        // programmingPercentageMap<编程题id，编程题总分占比（前端设置》
        Map<Long, Double> programmingPercentageMap = examPaper.getProgrammingQuestions().stream().collect(Collectors.toMap(ProgrammingQuestionDTO::getProblemId, ProgrammingQuestionDTO::getPercentage));
        // 客观题总分占比
        Map<Long, Double> percentageMap = questions.stream().collect(Collectors.toMap(ObjectiveQuestionDTO::getId, ObjectiveQuestionDTO::getPercentage));
        // map<题号，答案选项>
        Map<Long, List<VojObjectiveQuestionAnswer>> map = questions.stream().collect(Collectors.toMap(ObjectiveQuestionDTO::getId, VojObjectiveQuestion::getAnswers));
        // 所有用户提交的题目答案
        List<UserExamAns> examAnsByExamId = userExamAnswerService.getExamAnsByExamId(examId);
        // 编程题 collect<userId, 编程题用户作答列表> 选哪个写在content里
        Map<Long, List<UserExamAns>> collect = examAnsByExamId.stream().filter(e -> e.getQuestionType() == QuestionType.PROGRAMMING_QUESTION).collect(Collectors.groupingBy(UserExamAns::getUserId));
        // 客观题 objCollect
        Map<Long, List<UserExamAns>> objCollect = examAnsByExamId.stream().filter(e ->e.getQuestionType() == QuestionType.OBJECTIVE_QUESTION).collect(Collectors.groupingBy(UserExamAns::getUserId));
        // 如果考生没有写某道题需要生成一下对应的数据并保存（内容“”，分数0）
        generateUserAnswerObject(collect, exam, examPaper.getId(), examAnsByExamId);

        // 总，各题得分scoreMap<userId, score>  单一题目分数 singleScoreMap<userId, score>
        Map<Long, ExamUserScore> scoreMap = examUserScoreService.getByExamId(examId).stream().collect(Collectors.toMap(ExamUserScore::getUserId, e -> e));
        // 编程题总得分
        Map<Long, Double> singleScoreMap = examUserSingleQuesScoreService.getByExamId(examId).stream()
                .filter(e -> e.getQuestionType() == QuestionType.PROGRAMMING_QUESTION)  // 过滤器，筛选出编程题
                .collect(Collectors.groupingBy(ExamUserSingleQuesScore::getUserId, Collectors.reducing(0d, e -> Optional.ofNullable(e.getScore()).orElse(0d), Double::sum)));


        // 单选题得分
        List<ExamUserSingleQuesScore> scores = new ArrayList<>();
        // 批量处理用户作答的客观题，生成分数，分数状态信息
        for (Map.Entry<Long, List<UserExamAns>> entry : objCollect.entrySet()) {
            List<UserExamAns> value = entry.getValue();
            Long key = entry.getKey();
            ExamUserScore examUserScore = Optional.ofNullable(scoreMap.get(key)).orElseThrow(() -> ExceptionUtils.exception("未生成对应成绩对象"));
            examUserScore.setScore(singleScoreMap.get(key));
            for (UserExamAns e : value) {
                Long questionId = e.getQuestionId();
                Double percentage = percentageMap.get(questionId);
                // 用户答案
                /* List<ExamUserAnswerOption> options = e.getOptions();*/
                Long userOpition = Long.valueOf(e.getContent());

                // 选择题正确答案
                Map<Long, Boolean> answerMap = map.get(questionId).stream().collect(Collectors.toMap(VojObjectiveQuestionAnswer::getOptionId, v -> true));
                ExamUserSingleQuesScore singleQuesScore = Optional.ofNullable(e.getSingleQuesScore()).orElse(new ExamUserSingleQuesScore());
                if (singleQuesScore.getId() == null) {
                    singleQuesScore.setPaperId(examPaper.getId());
                    singleQuesScore.setExamId(examId);
                    singleQuesScore.setClassId(exam.getClassId());
                    singleQuesScore.setUserId(e.getUserId());
                    singleQuesScore.setQuestionId(e.getQuestionId());
                }

                /*int correctNum = 0;
                for (ExamUserAnswerOption option : options) {
                    if (!answerMap.containsKey(option.getOptionId())) {
                        correctNum = 0;
                        break;
                    }
                    correctNum = correctNum + 1;
                }*/
                // 获取作对数目
                int correctNum = 0;
                if (answerMap.containsKey(userOpition)){
                    LOGGER.info("right!!");
                    correctNum = 1;
                }
                singleQuesScore.setCreateTime(new Timestamp(System.currentTimeMillis()));
                singleQuesScore.setQuestionType(QuestionType.OBJECTIVE_QUESTION);
                singleQuesScore.setScore(correctNum * 1.0  * percentage * totalScore);
                scores.add(singleQuesScore);
                //examUserScore总分
                examUserScore.setScore(Optional.ofNullable(examUserScore.getScore()).orElse(0d) + singleQuesScore.getScore());
            }
            examUserScore.setStatus(ExamScoreStatus.ONE_CLICK_RATED);
        }
        // 插入数据库，更新客观题考试结果
        List<ExamUserScore> examUserScores = new ArrayList<>(scoreMap.values());
        examUserSingleQuesScoreService.insertOrUpdateList(scores);
        examUserScoreService.insertOrUpdateList(examUserScores);

        // 遍历 examAnsByExamId，更新examAnsbyExamId.singleQuesScore
        for (UserExamAns userExamAns: examAnsByExamId ) {
            try {
                List<ExamUserSingleQuesScore> singleQuesScore = examUserSingleQuesScoreService.getByUserExamQuestionId(userExamAns.getUserId(), userExamAns.getExamId(), userExamAns.getQuestionId());
                userExamAns.getSingleQuesScore().setUserId(singleQuesScore.get(singleQuesScore.size()-1).getUserId());
                userExamAns.getSingleQuesScore().setScore(singleQuesScore.get(singleQuesScore.size()-1).getScore());
            } catch (Exception e) {
                LOGGER.info(String.valueOf(e));
            }
        }

        List<UserExamAns> vojAnsList = examAnsByExamId.stream().filter(e -> e.getQuestionType() == QuestionType.PROGRAMMING_QUESTION).collect(Collectors.toList());
        List<ExamUserSingleQuesScore> singleScoreList = vojAnsList.stream().map(UserExamAns::getSingleQuesScore).collect(Collectors.toList());

        // 将结果数据传给用户画像接口 ，
        synchronizeExamUserProfiles(examId
                // 所有考生的总分信息
                , examUserScores
                // 编程题各题得分列表
                , singleScoreList
                // 试卷
                , examPaper
                // 编程题得分占比
                , programmingPercentageMap);
    }

    // 用户画像接口
    private void synchronizeExamUserProfiles(Long examId, List<ExamUserScore> scoreList, List<ExamUserSingleQuesScore> singleScoreList
            , ExamPaper examPaper, Map<Long, Double> percentageMap) {
        // 获取答题信息
        // 主要处理编程题
        List<ExamPortraitDto> dtoList = new ArrayList<>();
        Long paperId = examPaper.getId();
        Integer totalScore = examPaper.getTotalScore();
        // set限定不重复  problemPoints<问题id,包含的知识点> 不同问题包含的知识点map集合（不同题目的知识点可能重复），points set<所有问题包含的去重知识点> 所有问题包含的去重的知识点set集合（去重）
        // 编程题标签
        Map<Long, Set<String>> problemPoints = examPaperService.getProblemPoints(paperId);
        Set<String> points = problemPoints.values().stream().flatMap(Set::stream).collect(Collectors.toSet());
        // 编程题 collect<UserId, 编程题得分列表> repeatMap 查重数组
        Map<Long, List<ExamUserSingleQuesScore>> collect = singleScoreList.stream().collect(Collectors.groupingBy(ExamUserSingleQuesScore::getUserId));
        Map<Long, List<RepeatDTO>> repeatMap = this.getExamRepeat(examId).stream().collect(Collectors.groupingBy(RepeatDTO::getUserId));
        List<ExamSubmitLog> submitLogs = examSubmitLogService.getSubmitLogByExamId(examId);
        // 所有用户的所有提交记录  submitLogByUserId<userid, 提交记录>
        Map<Long, List<ExamSubmitLog>> submitLogByUserId = submitLogs.stream().collect(Collectors.groupingBy(ExamSubmitLog::getUserId));
        // submitLogById <userid, <问题id，提交记录>>
        Map<Long, Map<Long, List<ExamSubmitLog>>> submitLogById = submitLogs.stream().collect(Collectors.groupingBy(ExamSubmitLog::getUserId
                , Collectors.groupingBy(ExamSubmitLog::getQuestionId)));
        Map<Long, String> questionNameMap = examPaperService.getQuestionNameMap(paperId);
        // 遍历各题得分数组，生成用户画像
        for (ExamUserScore examUserScore : scoreList) {
            ExamPortraitDto examPortraitDto = new ExamPortraitDto();
            Long userId = examUserScore.getUserId();
            // 基础数据
            examPortraitDto.setExamId(examUserScore.getExamId());
            examPortraitDto.setUserId(userId);
            examPortraitDto.setUsername(examUserScore.getUsername());
            examPortraitDto.setCId(UserInfoShareHolder.getUserInfo().getCompanyId());
            // 总体的综合评价,  分数？
            examPortraitDto.setComprehensiveEvaluationDto(this.getEvaluationObject(BigDecimal.valueOf(Optional.ofNullable(examUserScore.getScore()).orElse(0d))));
            // 异常风险提示
            examPortraitDto.setAbnormalRiskWarningDto(this.getAbnormalRiskWarningObject(repeatMap.get(userId), submitLogById.get(userId)));
            // 技能图谱
            examPortraitDto.setSkillMapDtoList(this.getSkillMapDTOList(points, problemPoints, collect.get(examUserScore.getUserId()), percentageMap, totalScore,examUserScore.getUserId()));
            // TODO 编码能力
            examPortraitDto.setCodeCapacities(this.getCodeCapacities(collect.get(examUserScore.getUserId()), submitLogById.get(userId), questionNameMap, percentageMap, totalScore, examUserScore.getUserId()));
            // 每一道题目的运行情况
            examPortraitDto.setCodeSummarizeDtos(this.getCodeSummarizeDTOList(collect.get(examUserScore.getUserId()), submitLogByUserId, submitLogById.get(userId)));
            dtoList.add(examPortraitDto);
        }
        portraitService.examUserPortrait(dtoList);
    }

    // 未作答，则生成得分为0，内容为空的考试数据
    private void generateUserAnswerObject(Map<Long, List<UserExamAns>> map, Exam exam, Long paperId, List<UserExamAns> list) {
        // 根据paperId获取 试卷关联对象ExamPaperRelation，然后再获取到QuestionId，编程题id
        List<Long> questionIds = examPaperService.getQuestionIdByProgramming(paperId);
        if (CollectionUtils.isEmpty(questionIds)) {
            return;
        }
        // objects 客观题题目答案及得分等信息，scores 客观单项得分
        List<UserExamAns> objects = new ArrayList<>();
        List<ExamUserSingleQuesScore> scores = new ArrayList<>();
        // 遍历问题生成空答案空存放至objects  及分数0存放至scores
        for (Long questionId : questionIds) {
            for (Map.Entry<Long, List<UserExamAns>> entry : map.entrySet()) {
                Long userId = entry.getKey();
                // 把QuestionId 和客观题答案放在数组 value里
                Map<Long, UserExamAns> value = entry.getValue().stream().collect(Collectors.toMap(UserExamAns::getQuestionId, e -> e, (v1, v2) -> v1));
                // ？？ 如果value客观题信息集合中不包含编程题id，则添加 编程题为空
                if (!value.containsKey(questionId)) {
                    UserExamAns userExamAns = new UserExamAns();
                    ExamUserSingleQuesScore score = new ExamUserSingleQuesScore();
                    Timestamp currentTime = new Timestamp(System.currentTimeMillis());
                    // 生成试题答案
                    userExamAns.setSingleQuesScore(score);
                    userExamAns.setType(LanguageType.C);
                    userExamAns.setUserId(userId);
                    userExamAns.setExamId(exam.getId());
                    userExamAns.setPaperId(paperId);
                    userExamAns.setContent("");
                    userExamAns.setOptions(Collections.emptyList());
                    userExamAns.setQuestionType(QuestionType.PROGRAMMING_QUESTION);
                    userExamAns.setCreateTime(currentTime);
                    userExamAns.setQuestionId(questionId);
                    // 生成单道题分数
                    score.setUserId(userId);
                    score.setScore(0d);
                    score.setQuestionId(questionId);
                    score.setExamId(exam.getId());
                    score.setClassId(exam.getClassId());
                    score.setPaperId(paperId);
                    score.setCreateTime(currentTime);
                    score.setQuestionType(QuestionType.PROGRAMMING_QUESTION);
                    objects.add(userExamAns);
                    scores.add(score);
                }
            }

        }
        // 存入用户作答数据库
        if (!CollectionUtils.isEmpty(objects)) {
            userExamAnswerService.insertList(objects);
            list.addAll(objects);
        }
        if (CollectionUtils.isEmpty(scores)) {
            return;
        }
        examUserSingleQuesScoreService.insertOrUpdateList(scores);
    }
    // ？获取综合评价
    private ComprehensiveEvaluationDto getEvaluationObject(BigDecimal score) {
        ComprehensiveEvaluationDto dto = new ComprehensiveEvaluationDto();
        dto.setScore(score);
        return dto;
    }

    // 获取风险提示，传入用户和其他人的重复率 和 list <问题id，提交记录>
    // 返回 重复率和 从考试开始到题目提交的间隔
    private AbnormalRiskWarningDto getAbnormalRiskWarningObject(List<RepeatDTO> list, Map<Long, List<ExamSubmitLog>> submitLogById) {
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }

        AbnormalRiskWarningDto dto = new AbnormalRiskWarningDto();
        List<RepetitionRateDto> repetitionRateDtoList = new ArrayList<>();
        List<QuesTionIntervalDto> questionIntervalDTOs = new ArrayList<>();
        // 遍历用户和其他人的重复率
        for (RepeatDTO repeatDTO : list) {
            RepetitionRateDto repetitionRateDto = new RepetitionRateDto();
            repetitionRateDto.setRate(BigDecimal.valueOf(repeatDTO.getRepeatRate()));
            repetitionRateDto.setQuestionId(repeatDTO.getQuestionId());

            repetitionRateDto.setSelfSubmitTime(repeatDTO.getSubmitTime());
            repetitionRateDto.setTargetSubmitTime(repeatDTO.getTargetSubmitTime());

            System.out.println("kkkk " + repeatDTO.getTargetSubmitTime() + " " + repeatDTO.getSubmitTime());

            User user = userMapper.selectById(repeatDTO.getTargetUserId());
            repetitionRateDto.setDuplicateUsername(user.getUsername());
            repetitionRateDtoList.add(repetitionRateDto);

            // 问题提交时间间隔
            QuesTionIntervalDto quesTionIntervalDto = new QuesTionIntervalDto();
            quesTionIntervalDto.setQuestionId(repeatDTO.getQuestionId());
            // 时间间隔暂时不好统计 获取提交时间最晚的examSubmitLog
            ExamSubmitLog examSubmitLog = submitLogById.get(repeatDTO.getQuestionId()).stream().max(Comparator.comparing(ExamSubmitLog::getSubmitTime)).orElse(null);
            if (examSubmitLog == null) {
                continue;
            }
            // 间隔为 提交时间 - 考试开始时间
            quesTionIntervalDto.setTimeInterval(Long.valueOf(examSubmitLog.getSubmitTime().getTime() - examSubmitLog.getStartTime().getTime()).toString());
            questionIntervalDTOs.add(quesTionIntervalDto);
        }
        dto.setQuesTionIntervalDtos(questionIntervalDTOs);
        dto.setRepetitionRateDtoList(repetitionRateDtoList);
        return dto;
    }
    // 获取编码能力,暂时就是获取运行时间得分
    private List<CodeCapacity> getCodeCapacities(List<ExamUserSingleQuesScore> list, Map<Long, List<ExamSubmitLog>> submitLogById
            , Map<Long, String> questionNameMap, Map<Long, Double> percentageMap, Integer totalScore, Long userId) {
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        List<CodeCapacity> codeCapacities = new ArrayList<>();
        for (ExamUserSingleQuesScore score : list) {
            CodeCapacity codeCapacity = new CodeCapacity();
            // Score 单选题得分列表
            codeCapacity.setScore(BigDecimal.valueOf(Optional.ofNullable(score.getScore()).orElse(0d)));
            codeCapacity.setQuestionId(score.getQuestionId());
            // QuestionSore 该题得分占比*考试总分 =  编程题分数
            codeCapacity.setQuestionSore(new BigDecimal(percentageMap.get(score.getQuestionId()) * totalScore));
            //  运行时间
            List<ExamSubmitLog> examSubmitLogs = Optional.ofNullable(submitLogById.get(score.getQuestionId())).orElse(Collections.emptyList());
            ExamSubmitLog examSubmitLog = examSubmitLogs.stream().max(Comparator.comparing(ExamSubmitLog::getSubmitTime)).orElse(null);
            if (examSubmitLog == null) {
                continue;
            }
            codeCapacity.setRunTime(Long.valueOf(Optional.ofNullable(examSubmitLog.getExecuteTime()).orElse(Integer.MAX_VALUE)));
            codeCapacity.setQuestionName(questionNameMap.get(score.getQuestionId()));
            codeCapacity.setUserId(userId);
            codeCapacities.add(codeCapacity);

        }
        // 计算代码规范性 ，用所有的代码规范性除以所有的代码数量
        int sum = 0;
        int codeScore = 0;
        for (Long id : submitLogById.keySet()){
            List<ExamSubmitLog> examSubmitLogs = submitLogById.get(id);
            for (int i = 0; i < examSubmitLogs.size(); i++) {
                String content = examSubmitLogs.get(i).getContent();
                if(!content.isEmpty() && !content.equals("")){
                    sum++;
                    LanguageType type = examSubmitLogs.get(i).getType();
                    if(type == LanguageType.C || type == LanguageType.C_PLUS_PLUS){
                        codeScore += CodeRegularityUtil.analyzeCode(content, "C++");
                    }else if(type == LanguageType.JAVA){
                        codeScore += CodeRegularityUtil.analyzeCode(content, "Java");
                    }else if(type == LanguageType.PYTHON3){
                        codeScore += CodeRegularityUtil.analyzeCode(content, "Python");
                    }else {
                        codeScore += CodeRegularityUtil.analyzeCode(content, "ruby");
                    }
                }
            }
        }
        // 计算代码规范性评分
        BigDecimal codeSpecification;
        // 避免除数为0的情况
        if (sum != 0) {
            codeSpecification = BigDecimal.valueOf(codeScore)
                    .divide(BigDecimal.valueOf(sum), BigDecimal.ROUND_HALF_UP);
        } else {
            codeSpecification  = BigDecimal.ZERO; // 或者处理其他逻辑
        }
        codeCapacities.forEach(e -> e.setCodeSpecification(codeSpecification));

        return codeCapacities;
    }
    // 每一道题目的运行情况
    // submitLogByUserId<userId, <userid,提交记录> 所有用户的提交情况
    // submitLogById<questionId, <questionid,提交记录> 某个用户的所有编程题提交情况
    private List<CodeSummarizeDto> getCodeSummarizeDTOList(List<ExamUserSingleQuesScore> list
            , Map<Long, List<ExamSubmitLog>> submitLogByUserId, Map<Long, List<ExamSubmitLog>> submitLogById) {
        List<CodeSummarizeDto> dtoList = new ArrayList<>();
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        // 遍历某个用户（传过来的）的各编程题情况
        for (ExamUserSingleQuesScore score : list) {
            // TODO 为空考虑
            CodeSummarizeDto codeSummarizeDto = new CodeSummarizeDto();
            codeSummarizeDto.setQuestionId(score.getQuestionId());
            codeSummarizeDto.setScore(BigDecimal.valueOf(Optional.ofNullable(score.getScore()).orElse(0d)));
            //
            List<ExamSubmitLog> userSubmits = Optional.ofNullable(submitLogByUserId.get(score.getUserId())).orElse(new ArrayList<>());
            Integer submitCount = 0;
            for(ExamSubmitLog userSubmit: userSubmits) {
                if (userSubmit.getQuestionId().equals(score.getQuestionId())) {
                    ++submitCount;
                }
            }
            codeSummarizeDto.setSubmitCount(submitCount);
            List<ExamSubmitLog> examSubmitLogs = Optional.ofNullable(submitLogById.get(score.getQuestionId())).orElse(Collections.emptyList());
            ExamSubmitLog examSubmitLog = examSubmitLogs.stream().max(Comparator.comparing(ExamSubmitLog::getSubmitTime)).orElse(null);
            if (examSubmitLog == null) {
                continue;
            }
            codeSummarizeDto.setLanguage(examSubmitLog.getType().getText());
            codeSummarizeDto.setUsedMemory(BigDecimal.valueOf(Optional.ofNullable(examSubmitLog.getUserMemory()).orElse(Integer.MAX_VALUE)));
            codeSummarizeDto.setRunTime(Long.valueOf(Optional.ofNullable(examSubmitLog.getExecuteTime()).orElse(Integer.MAX_VALUE)));
            codeSummarizeDto.setUserId(score.getUserId());
            dtoList.add(codeSummarizeDto);
        }
        return dtoList;
    }
    // 获取技能图谱
    private List<SkillMapDto> getSkillMapDTOList(Set<String> points, Map<Long, Set<String>> problemPoints, List<ExamUserSingleQuesScore> list
            , Map<Long, Double> percentageMap, Integer totalScore, Long userId) {
        List<SkillMapDto> dtoList = new ArrayList<>();
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        // TODO 获取知识点
        // 遍历知识点集合（题目中包含的去重知识点）
        for (String point : points) {
            double score = 0;
            double sum = 0;
            // 对每个知识点，遍历用户各题得分情况
            for (ExamUserSingleQuesScore examUserSingleQuesScore : list) {
                // 对每一个题，获取该题的知识点
                Set<String> strings = Optional.ofNullable(problemPoints.get(examUserSingleQuesScore.getQuestionId())).orElse(new HashSet<>());
                // 如果这一个题包含遍历的知识点，则计数算分
                if (strings.contains(point)) {  // score 用户得分，sum 为编程题的总分）
                    score += Optional.ofNullable(examUserSingleQuesScore.getScore()).orElse(0d);
                    sum += percentageMap.get(examUserSingleQuesScore.getQuestionId()) * totalScore;
                }
            }
            if (Double.compare(sum, 0d) == 0) {
                continue;
            }
            // 用这个分数合和权重合数 来画出技能图谱
            SkillMapDto skillMapDto = new SkillMapDto();
            // ActualSumScore 实际分
            skillMapDto.setActualSumScore(new BigDecimal(score));
            skillMapDto.setTagName(point);
            // SumScore 总分（百分比算出来的分）
            skillMapDto.setSumScore(new BigDecimal(sum));
            skillMapDto.setUserId(userId);
            dtoList.add(skillMapDto);
        }
        return dtoList;
    }
    // 查重，看不同用户作答的重复率。返回重复数组（每个人都有一个和自己代码重复率最高的人，两者一起绑定add到list 里，然后返回）
    @Override
    public List<RepeatDTO> getExamRepeat(Long examId) {
        // 获取所有用户所有题目作答情况
        List<UserExamAns> examAnsByExamId = userExamAnswerService.getExamAnsByExamId(examId);
        // collect<问题id，用户编程题作答集合>
        Map<Long, List<UserExamAns>> collect = examAnsByExamId.stream()
                .filter(e -> e.getQuestionType() == QuestionType.PROGRAMMING_QUESTION)
                .collect(Collectors.groupingBy(UserExamAns::getQuestionId));

        List<RepeatDTO> list = new ArrayList<>();
        // 遍历 collect
        for (Map.Entry<Long, List<UserExamAns>> entry : collect.entrySet()) {
            Long key = entry.getKey();
            List<UserExamAns> value = entry.getValue();
            // 遍历各用户的各题作答集合
            for (UserExamAns answerOne : value) {
                RepeatDTO dto = new RepeatDTO();
                dto.setExamId(answerOne.getExamId());
                dto.setQuestionId(key);
                dto.setRepeatRate(0d);
                dto.setUserId(answerOne.getUserId());
                dto.setContent(answerOne.getContent());
//                dto.setSubmitTime(answerOne.getCreateTime());
                long uid = answerOne.getUserId();
                long problemId = key;

                System.out.println(uid + " " + problemId);

                LambdaQueryWrapper<VojSubmissions> queryWrapper = new LambdaQueryWrapper<VojSubmissions>()
                        .eq(VojSubmissions::getProblemId, problemId)
                        .eq(VojSubmissions::getUid, uid)
                        .eq(VojSubmissions::getSourceModule, "exam")
                        .orderByDesc(VojSubmissions::getSubmissionSubmitTime)
                        .select(VojSubmissions::getSubmissionSubmitTime)
                        .last("LIMIT 1");

                dto.setSubmitTime(new Timestamp(vojSubmissionsMapper.selectOne(queryWrapper).getSubmissionSubmitTime().getTime()));
                // 再遍历其他用户的作答集合，
                for (UserExamAns answerOther : value) {
                    if (Objects.equals(answerOne.getUserId(), answerOther.getUserId())) {
                        continue;
                    }
                    // 获取 重复率
                    double rate = RepetitionRate.computeByLCS(answerOne.getContent(), answerOther.getContent());
                    // 和谁最像，挑出来
                    if (dto.getRepeatRate() < rate) {

                        uid = answerOther.getUserId();
                        problemId = answerOther.getQuestionId();

                        dto.setTargetContent(answerOther.getContent());
//                        dto.setTargetSubmitTime(answerOther.getCreateTime());
                        dto.setTargetUserId(answerOther.getUserId());
                        dto.setRepeatRate(rate);
                    }
                }
                System.out.println(uid + " fjdsfksdfjslfs  " + problemId);
                LambdaQueryWrapper<VojSubmissions> queryWrapper2 = new LambdaQueryWrapper<VojSubmissions>()
                        .eq(VojSubmissions::getProblemId, problemId)
                        .eq(VojSubmissions::getUid, uid)
                        .eq(VojSubmissions::getSourceModule, "exam")
                        .orderByDesc(VojSubmissions::getSubmissionSubmitTime)
                        .select(VojSubmissions::getSubmissionSubmitTime)
                        .last("LIMIT 1");
                dto.setTargetSubmitTime(new Timestamp(vojSubmissionsMapper.selectOne(queryWrapper2).getSubmissionSubmitTime().getTime()));
                list.add(dto);
            }
        }
        return list;
    }


    @Override
    public List<Exam> getRecentExam(Paging paging) {
        if (paging == null) {
            return Collections.emptyList();
        }
        User user = UserInfoShareHolder.getUserInfo().getUser();
        Long id = user.getId();
        Clause clause = StringUtils.equals(user.getRole(), RoleEnum.ADMIN.getIdentify()) ? null : SingleClause.equal("creator_id", id);
        return super.selectPage(clause, paging, Sorting.descending("start_time")).getData();
    }

    @Override
    public List<Exam> getExamByClause(Clause clause) {
        if (clause == null) {
            return Collections.emptyList();
        }
        return super.selectList(clause);
    }

    @Override
    public void updateExamAttendNum(Long examId) {
        Exam exam = mapper.selectById(examId);
        exam.setAttendNum(exam.getAttendNum() + 1);
        mapper.updateById(exam);
    }


    private void limit(Long examId) {
        UserCache userInfo = UserInfoShareHolder.getUserInfo();
        Long companyId = userInfo.getCompanyId();
        // 从数据库中获取batch+companyId+examId的信息
        boolean flag = (boolean) Optional.ofNullable(redisTemplate.opsForHash().get(BATCH_ONE_CLICK, companyId + "__" + examId)).orElse(false);
        // 如果数据库中有这个，则暂时不能给他评分
        if (flag) {
            throw ExceptionUtils.exception("已评分，需间隔十分钟后可再次评分");
        }
        // 没有就存入数据库,redisTemplate就是用于操作数据库的 详情见https://blog.csdn.net/lydms/article/details/105224210
        HashOperations<Object, Object, Object> ops = redisTemplate.opsForHash();
        Map<String, Object> map = new HashMap<>();
        map.put(companyId + "__" + examId, true);
        ops.putAll(BATCH_ONE_CLICK, map);
        // 指定这个key 的过期时间 10 分钟
        redisTemplate.expire(BATCH_ONE_CLICK, 10, TimeUnit.MINUTES);
    }

}
